using System;
using System.Collections.Generic;
using System.Text;
using Barrage.Shaders;
using SlimDX.Direct3D9;
using Barrage.Global.Vertex;
using Barrage.Global;
using SlimDX;

namespace Barrage.Meshes.Text
{
    /// <summary>
    /// Structure that holds information about how to draw a TextMesh
    /// </summary>
    public struct TextDrawProperties
    {
        /// <summary>
        /// Text scale (default is 1)
        /// </summary>
        public float Scale;
        /// <summary>
        /// Text offset
        /// </summary>
        public Vector2 Offset;
        /// <summary>
        /// Constructor. Inits values
        /// </summary>
        /// <param name="scale">Text scale</param>
        /// <param name="offset">Text offset</param>
        public TextDrawProperties(float scale,Vector2 offset)
        {
            Scale = scale;
            Offset = offset;
        }
    }

    /// <summary>
    /// Mesh that renders text with the style defined by a BitmapFont
    /// </summary>
    public class TextMesh : GuiMesh
    {
        const int MAX_TEXT_SIZE = 1024;
        BitmapFont font;

        bool dirty = false;
        /// <summary>
        /// Inits the mesh with default drawing properties
        /// </summary>
        /// <param name="font">Font used to render text</param>
        public TextMesh(BitmapFont font):this(font,new TextDrawProperties(1,Vector2.Zero))
        {

        }

        /// <summary>
        /// Inits the mesh with the specified drawing properties
        /// </summary>
        /// <param name="font">Font used to render text</param>
        /// <param name="properties">Drawing properties</param>
        public TextMesh(BitmapFont font,TextDrawProperties properties)
        {
            this.drawProperties = properties;
            this.font = font;
            materials[0].Texture = font.Texture;
            if (indexBuffer==null)
                initializeIndexBuffer();
            initializeBuffer();

            Engine.RegisterDisposeDefaultPoolHandler(new EngineDisposeHandler(DisposeTextBuffer));
            Engine.RegisterRecreateDefaultPoolHandler(new EngineDisposeHandler(initializeBuffer));
            
        }

        string text;
        /// <summary>
        /// Gets / Sets the rendered text
        /// </summary>
        public string Text { get { return text; } set { text = value; dirty = true; } }

        VertexPositionUV[] textData = new VertexPositionUV[MAX_TEXT_SIZE];


        static IndexBuffer indexBuffer = null;
        VertexBuffer textBuffer;

        TextDrawProperties drawProperties;
        /// <summary>
        /// Text drawing properties
        /// </summary>
        public TextDrawProperties DrawProperties { get { return drawProperties; } set { drawProperties = value; dirty = true; } }
        /// <summary>
        /// Text offset
        /// </summary>
        public Vector2 Offset { get { return drawProperties.Offset; } set { drawProperties.Offset = value; } }

        int charCount = 0;

        static void initializeIndexBuffer()
        {
            indexBuffer = new IndexBuffer(Engine.Device, 4 * 6 * MAX_TEXT_SIZE, Usage.WriteOnly, Pool.Managed, false);
            DataStream stream = indexBuffer.Lock(0, 0, LockFlags.None);
            int[] indices = new int[MAX_TEXT_SIZE * 6];
            for (int i = 0; i < MAX_TEXT_SIZE; i++)
            {
                int baseIndex = i * 6;
                int vertexIndex = i * 4;
                indices[baseIndex] = vertexIndex;
                indices[baseIndex + 1] = vertexIndex + 1;
                indices[baseIndex + 2] = vertexIndex + 2;
                indices[baseIndex + 3] = vertexIndex + 2;
                indices[baseIndex + 4] = vertexIndex + 3;
                indices[baseIndex + 5] = vertexIndex + 1;
            };
            stream.WriteRange(indices);
            indexBuffer.Unlock();

            Engine.RegisterDisposeHandler(new EngineDisposeHandler(disposeIndexBuffer));
        }

        static void disposeIndexBuffer()
        {
            if (indexBuffer != null)
                indexBuffer.Dispose();
            indexBuffer = null;
        }

        void initializeBuffer()
        {
            textBuffer = new VertexBuffer(Engine.Device, VertexPositionUV.SizeBytes * 4 * MAX_TEXT_SIZE, Usage.WriteOnly | Usage.Dynamic, VertexPositionUV.Format, Pool.Default);
        }

        void updateData()
        {
            Vector2 vOrigin = new Vector2(0, 0);
            charCount = 0;
            //int pxWidth = 0;
            Vector2 vAt = Vector2.Zero;
            // draw each character in the string
            foreach (char c in text)
            {
                int baseIndex = 4 * charCount;
                if (!font.CharInfo.ContainsKey(c))
                {
                    //TODO: print out undefined char glyph
                    continue;
                }

                CharInfo cinfo = font.CharInfo[c];

                // draw the glyph
                vAt.X += cinfo.XOffset * drawProperties.Scale;
                if (cinfo.Width != 0 && cinfo.Height != 0)
                {
                    textData[baseIndex + 2].UV = new Vector2(cinfo.X / (float)font.FontInfo.ScaleW, cinfo.Y / (float)font.FontInfo.ScaleH);
                    textData[baseIndex + 3].UV = new Vector2((cinfo.X + cinfo.Width) / (float)font.FontInfo.ScaleW, cinfo.Y / (float)font.FontInfo.ScaleH);
                    textData[baseIndex].UV = new Vector2(cinfo.X / (float)font.FontInfo.ScaleW, (cinfo.Y + cinfo.Height) / (float)font.FontInfo.ScaleH);
                    textData[baseIndex + 1].UV = new Vector2((cinfo.X + cinfo.Width) / (float)font.FontInfo.ScaleW, (cinfo.Y + cinfo.Height) / (float)font.FontInfo.ScaleH);

                    textData[baseIndex].Position = new Vector3(vAt.X, vAt.Y - cinfo.Height * drawProperties.Scale / 2f, 0);
                    textData[baseIndex + 1].Position = new Vector3(vAt.X + cinfo.Width * drawProperties.Scale, vAt.Y - cinfo.Height * drawProperties.Scale / 2f, 0);
                    textData[baseIndex + 2].Position = new Vector3(vAt.X, vAt.Y + cinfo.Height * drawProperties.Scale / 2f, 0);
                    textData[baseIndex + 3].Position = new Vector3(vAt.X + cinfo.Width * drawProperties.Scale, vAt.Y + cinfo.Height * drawProperties.Scale / 2f, 0);

                    textData[baseIndex].Position += new Vector3(drawProperties.Offset,0);
                    textData[baseIndex + 1].Position += new Vector3(drawProperties.Offset,0);
                    textData[baseIndex + 2].Position += new Vector3(drawProperties.Offset,0);
                    textData[baseIndex + 3].Position += new Vector3(drawProperties.Offset,0);
                }

                // update the string width and advance the pen to the next drawing position
                vAt.X += cinfo.XAdvance * drawProperties.Scale - cinfo.XOffset * drawProperties.Scale;

                charCount++;
            }

            DataStream stream = textBuffer.Lock(0, 4  * text.Length * VertexPositionUV.SizeBytes, LockFlags.Discard);
            stream.WriteRange(textData, 0, 4 * text.Length);
            textBuffer.Unlock();

        }
        /// <summary>
        /// \internal Renders the text
        /// </summary>
        /// <param name="item">Item that's rendering this mesh</param>
        public override void Render(Item item)
        {
            if (dirty) { updateData(); }
            if (charCount <= 0)
                return;
            shader.BeginRender(item, materials[0]);
            Engine.Device.VertexFormat = VertexPositionUV.Format;
            Engine.Device.Indices = indexBuffer;
            Engine.Device.SetStreamSource(0, textBuffer, 0, VertexPositionUV.SizeBytes);
            int passNumber = shader.PassNumber(materials[0]);
            for (int j = 0; j < passNumber; j++)
            {
                shader.BeginPass(j);
                Engine.Device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 4 * charCount, 0, 2 * charCount);
                shader.EndPass();
            }
            shader.EndRender();
        }

        /// <summary>
        /// \internal Clear resources
        /// </summary>
        public override void Dispose()
        {
            Engine.UnregisterDisposeDefaultPoolHandler(new EngineDisposeHandler(DisposeTextBuffer));
            Engine.UnregisterRecreateDefaultPoolHandler(new EngineDisposeHandler(initializeBuffer));
            font = null;
            textData = null;
            DisposeTextBuffer();
            base.Dispose();
        }

        void DisposeTextBuffer()
        {
            textBuffer.Dispose();
            textBuffer = null;
        }
    }
}
